跳到內容

Eloquent:開始使用

簡介

Laravel 包含了 Eloquent,一個物件關聯對應 (ORM) 工具,讓您能夠愉快地與資料庫互動。當使用 Eloquent 時,每個資料庫表都有一個對應的「模型」,用於與該表互動。除了從資料庫表中檢索記錄外,Eloquent 模型還允許您從表中插入、更新和刪除記錄。

在開始之前,請務必在應用程式的 config/database.php 設定檔中設定資料庫連線。有關設定資料庫的更多資訊,請查看資料庫設定文件

產生模型類別

為了開始使用,讓我們建立一個 Eloquent 模型。模型通常位於 app\Models 目錄中,並擴展 Illuminate\Database\Eloquent\Model 類別。您可以使用 make:model Artisan 命令來產生新的模型

1php artisan make:model Flight

如果您想在產生模型時同時產生資料庫遷移,您可以使用 --migration-m 選項

1php artisan make:model Flight --migration

您可以在產生模型時產生各種其他類型的類別,例如 factories、seeders、policies、controllers 和 form requests。此外,這些選項可以組合起來一次建立多個類別

1# Generate a model and a FlightFactory class...
2php artisan make:model Flight --factory
3php artisan make:model Flight -f
4 
5# Generate a model and a FlightSeeder class...
6php artisan make:model Flight --seed
7php artisan make:model Flight -s
8 
9# Generate a model and a FlightController class...
10php artisan make:model Flight --controller
11php artisan make:model Flight -c
12 
13# Generate a model, FlightController resource class, and form request classes...
14php artisan make:model Flight --controller --resource --requests
15php artisan make:model Flight -crR
16 
17# Generate a model and a FlightPolicy class...
18php artisan make:model Flight --policy
19 
20# Generate a model and a migration, factory, seeder, and controller...
21php artisan make:model Flight -mfsc
22 
23# Shortcut to generate a model, migration, factory, seeder, policy, controller, and form requests...
24php artisan make:model Flight --all
25php artisan make:model Flight -a
26 
27# Generate a pivot model...
28php artisan make:model Member --pivot
29php artisan make:model Member -p

檢查模型

有時,僅僅瀏覽模型的程式碼可能難以確定模型的所有可用屬性和關聯。您可以嘗試使用 model:show Artisan 命令,它可以方便地概覽模型的所有屬性和關聯

1php artisan model:show Flight

Eloquent 模型慣例

make:model 命令產生的模型將放置在 app/Models 目錄中。讓我們檢查一個基本的模型類別,並討論一些 Eloquent 的關鍵慣例

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 // ...
10}

資料表名稱

在瀏覽上面的範例後,您可能已經注意到我們沒有告訴 Eloquent 哪個資料庫表對應於我們的 Flight 模型。依照慣例,除非明確指定另一個名稱,否則將使用類別名稱的「蛇底式命名法」複數形式作為表名。因此,在本例中,Eloquent 將假設 Flight 模型將記錄儲存在 flights 表中,而 AirTrafficController 模型將記錄儲存在 air_traffic_controllers 表中。

如果您的模型對應的資料庫表不符合此慣例,您可以透過在模型上定義 table 屬性來手動指定模型的表名

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The table associated with the model.
11 *
12 * @var string
13 */
14 protected $table = 'my_flights';
15}

主鍵

Eloquent 也會假設每個模型對應的資料庫表都有一個名為 id 的主鍵欄位。如有必要,您可以在模型上定義受保護的 $primaryKey 屬性,以指定另一個欄位作為模型的主鍵

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The primary key associated with the table.
11 *
12 * @var string
13 */
14 protected $primaryKey = 'flight_id';
15}

此外,Eloquent 假設主鍵是一個遞增的整數值,這表示 Eloquent 會自動將主鍵轉換為整數。如果您希望使用非遞增或非數字的主鍵,您必須在模型上定義一個 public $incrementing 屬性,並將其設定為 false

1<?php
2 
3class Flight extends Model
4{
5 /**
6 * Indicates if the model's ID is auto-incrementing.
7 *
8 * @var bool
9 */
10 public $incrementing = false;
11}

如果您的模型主鍵不是整數,您應該在模型上定義一個受保護的 $keyType 屬性。此屬性應具有 string

1<?php
2 
3class Flight extends Model
4{
5 /**
6 * The data type of the primary key ID.
7 *
8 * @var string
9 */
10 protected $keyType = 'string';
11}

「複合」主鍵

Eloquent 要求每個模型至少有一個唯一識別的「ID」,可以作為其主鍵。「複合」主鍵不受 Eloquent 模型支援。但是,除了資料表的唯一識別主鍵之外,您可以自由地向資料庫表新增額外的多欄、唯一索引。

UUID 和 ULID 鍵

您可以選擇使用 UUID 而不是自動遞增整數作為 Eloquent 模型的主鍵。UUID 是通用唯一字母數字識別碼,長度為 36 個字元。

如果您希望模型使用 UUID 鍵而不是自動遞增整數鍵,您可以在模型上使用 Illuminate\Database\Eloquent\Concerns\HasUuids trait。當然,您應該確保模型具有UUID 等效的主鍵欄位

1use Illuminate\Database\Eloquent\Concerns\HasUuids;
2use Illuminate\Database\Eloquent\Model;
3 
4class Article extends Model
5{
6 use HasUuids;
7 
8 // ...
9}
10 
11$article = Article::create(['title' => 'Traveling to Europe']);
12 
13$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"

預設情況下,HasUuids trait 將為您的模型產生「有序」UUID。這些 UUID 對於索引資料庫儲存更有效率,因為它們可以按字典順序排序。

您可以透過在模型上定義 newUniqueId 方法來覆寫給定模型的 UUID 產生過程。此外,您可以透過在模型上定義 uniqueIds 方法來指定應接收 UUID 的欄位

1use Ramsey\Uuid\Uuid;
2 
3/**
4 * Generate a new UUID for the model.
5 */
6public function newUniqueId(): string
7{
8 return (string) Uuid::uuid4();
9}
10 
11/**
12 * Get the columns that should receive a unique identifier.
13 *
14 * @return array<int, string>
15 */
16public function uniqueIds(): array
17{
18 return ['id', 'discount_code'];
19}

如果您願意,您可以選擇使用「ULID」而不是 UUID。ULID 類似於 UUID;但是,它們的長度僅為 26 個字元。與有序 UUID 類似,ULID 可按字典順序排序,以便進行有效的資料庫索引。若要使用 ULID,您應該在模型上使用 Illuminate\Database\Eloquent\Concerns\HasUlids trait。您也應該確保模型具有ULID 等效的主鍵欄位

1use Illuminate\Database\Eloquent\Concerns\HasUlids;
2use Illuminate\Database\Eloquent\Model;
3 
4class Article extends Model
5{
6 use HasUlids;
7 
8 // ...
9}
10 
11$article = Article::create(['title' => 'Traveling to Asia']);
12 
13$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"

時間戳記

預設情況下,Eloquent 預期 created_atupdated_at 欄位存在於模型對應的資料庫表中。當建立或更新模型時,Eloquent 將自動設定這些欄位的值。如果您不希望這些欄位由 Eloquent 自動管理,您應該在模型上定義一個 $timestamps 屬性,其值為 false

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * Indicates if the model should be timestamped.
11 *
12 * @var bool
13 */
14 public $timestamps = false;
15}

如果您需要自訂模型時間戳記的格式,請在模型上設定 $dateFormat 屬性。此屬性決定了日期屬性在資料庫中的儲存方式,以及模型序列化為陣列或 JSON 時的格式

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The storage format of the model's date columns.
11 *
12 * @var string
13 */
14 protected $dateFormat = 'U';
15}

如果您需要自訂用於儲存時間戳記的欄位名稱,您可以在模型上定義 CREATED_ATUPDATED_AT 常數

1<?php
2 
3class Flight extends Model
4{
5 const CREATED_AT = 'creation_date';
6 const UPDATED_AT = 'updated_date';
7}

如果您想在不修改模型的 updated_at 時間戳記的情況下執行模型操作,您可以在提供給 withoutTimestamps 方法的閉包中操作模型

1Model::withoutTimestamps(fn () => $post->increment('reads'));

資料庫連線

預設情況下,所有 Eloquent 模型都將使用為您的應用程式設定的預設資料庫連線。如果您想指定在與特定模型互動時應使用的不同連線,您應該在模型上定義 $connection 屬性

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The database connection that should be used by the model.
11 *
12 * @var string
13 */
14 protected $connection = 'mysql';
15}

預設屬性值

預設情況下,新實例化的模型實例將不包含任何屬性值。如果您想為模型的某些屬性定義預設值,您可以在模型上定義 $attributes 屬性。放置在 $attributes 陣列中的屬性值應採用其原始的「可儲存」格式,就像剛從資料庫讀取一樣

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The model's default values for attributes.
11 *
12 * @var array
13 */
14 protected $attributes = [
15 'options' => '[]',
16 'delayed' => false,
17 ];
18}

設定 Eloquent 嚴格模式

Laravel 提供了多種方法,讓您可以設定 Eloquent 的行為和在各種情況下的「嚴格性」。

首先,preventLazyLoading 方法接受一個可選的布林值參數,指示是否應防止延遲載入。例如,您可能希望僅在非生產環境中停用延遲載入,以便即使生產程式碼中意外出現延遲載入的關聯,您的生產環境也能繼續正常運作。通常,此方法應在應用程式的 AppServiceProviderboot 方法中調用

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventLazyLoading(! $this->app->isProduction());
9}

此外,您可以指示 Laravel 在嘗試填充不可填充的屬性時拋出例外,方法是調用 preventSilentlyDiscardingAttributes 方法。這可以幫助防止在嘗試設定尚未新增到模型的 fillable 陣列中的屬性時,在本地開發期間發生意外錯誤

1Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());

檢索模型

一旦您建立了一個模型和其相關聯的資料庫表,您就可以開始從資料庫中檢索資料了。您可以將每個 Eloquent 模型視為一個強大的查詢建構器,讓您可以流暢地查詢與模型關聯的資料庫表。模型的 all 方法將檢索模型關聯的資料庫表中的所有記錄

1use App\Models\Flight;
2 
3foreach (Flight::all() as $flight) {
4 echo $flight->name;
5}

建構查詢

Eloquent all 方法將傳回模型表中的所有結果。但是,由於每個 Eloquent 模型都充當查詢建構器,因此您可以向查詢新增其他約束,然後調用 get 方法來檢索結果

1$flights = Flight::where('active', 1)
2 ->orderBy('name')
3 ->take(10)
4 ->get();

由於 Eloquent 模型是查詢建構器,因此您應該查看 Laravel 的查詢建構器提供的所有方法。您可以在編寫 Eloquent 查詢時使用任何這些方法。

重新整理模型

如果您已經有一個從資料庫檢索的 Eloquent 模型實例,您可以使用 freshrefresh 方法「重新整理」模型。fresh 方法將從資料庫重新檢索模型。現有的模型實例將不受影響

1$flight = Flight::where('number', 'FR 900')->first();
2 
3$freshFlight = $flight->fresh();

refresh 方法將使用來自資料庫的新鮮資料重新水合現有模型。此外,它的所有已載入關聯也將被重新整理

1$flight = Flight::where('number', 'FR 900')->first();
2 
3$flight->number = 'FR 456';
4 
5$flight->refresh();
6 
7$flight->number; // "FR 900"

集合

正如我們所看到的,Eloquent 方法(如 allget)從資料庫中檢索多個記錄。但是,這些方法不會傳回純 PHP 陣列。相反,傳回的是 Illuminate\Database\Eloquent\Collection 的實例。

Eloquent 的 Collection 類別擴展了 Laravel 的基礎 Illuminate\Support\Collection 類別,後者提供了多種實用的方法來與資料集合互動。例如,reject 方法可用於根據調用閉包的結果從集合中移除模型。

1$flights = Flight::where('destination', 'Paris')->get();
2 
3$flights = $flights->reject(function (Flight $flight) {
4 return $flight->cancelled;
5});

除了 Laravel 基礎集合類別提供的方法之外,Eloquent 集合類別還提供了一些額外的方法,這些方法專門用於與 Eloquent 模型集合互動。

由於 Laravel 的所有集合都實作了 PHP 的可迭代介面,因此您可以像迴圈陣列一樣遍歷集合。

1foreach ($flights as $flight) {
2 echo $flight->name;
3}

分塊結果

如果您嘗試透過 allget 方法載入數以萬計的 Eloquent 記錄,您的應用程式可能會耗盡記憶體。與其使用這些方法,不如使用 chunk 方法來更有效率地處理大量模型。

chunk 方法將檢索 Eloquent 模型的一個子集,並將它們傳遞給一個閉包以進行處理。由於一次只檢索目前區塊的 Eloquent 模型,因此在使用大量模型時,chunk 方法將顯著降低記憶體使用量。

1use App\Models\Flight;
2use Illuminate\Database\Eloquent\Collection;
3 
4Flight::chunk(200, function (Collection $flights) {
5 foreach ($flights as $flight) {
6 // ...
7 }
8});

傳遞給 chunk 方法的第一個參數是您希望每個「區塊」接收的記錄數量。作為第二個參數傳遞的閉包將針對從資料庫檢索的每個區塊調用。將執行資料庫查詢以檢索傳遞給閉包的每個記錄區塊。

如果您要根據一個您也會在迭代結果時更新的欄位來篩選 chunk 方法的結果,則應該使用 chunkById 方法。在這些情況下使用 chunk 方法可能會導致意外和不一致的結果。在內部,chunkById 方法將始終檢索 id 欄位大於前一個區塊中最後一個模型的模型。

1Flight::where('departed', true)
2 ->chunkById(200, function (Collection $flights) {
3 $flights->each->update(['departed' => false]);
4 }, column: 'id');

由於 chunkByIdlazyById 方法會將它們自己的 "where" 條件添加到正在執行的查詢中,因此您通常應該在閉包中邏輯分組您自己的條件。

1Flight::where(function ($query) {
2 $query->where('delayed', true)->orWhere('cancelled', true);
3})->chunkById(200, function (Collection $flights) {
4 $flights->each->update([
5 'departed' => false,
6 'cancelled' => true
7 ]);
8}, column: 'id');

使用惰性集合分塊

lazy 方法的工作方式與chunk 方法類似,從底層來看,它也是以區塊方式執行查詢。但是,lazy 方法不是像 chunk 方法那樣將每個區塊直接傳遞到回呼中,而是返回一個扁平化的 LazyCollection Eloquent 模型,讓您可以將結果作為單一串流進行互動。

1use App\Models\Flight;
2 
3foreach (Flight::lazy() as $flight) {
4 // ...
5}

如果您要根據一個您也會在迭代結果時更新的欄位來篩選 lazy 方法的結果,則應該使用 lazyById 方法。在內部,lazyById 方法將始終檢索 id 欄位大於前一個區塊中最後一個模型的模型。

1Flight::where('departed', true)
2 ->lazyById(200, column: 'id')
3 ->each->update(['departed' => false]);

您可以使用 lazyByIdDesc 方法根據 id 的降序來篩選結果。

Cursors(游標)

lazy 方法類似,當您迭代數以萬計的 Eloquent 模型記錄時,可以使用 cursor 方法來顯著降低應用程式的記憶體消耗。

cursor 方法只會執行單一資料庫查詢;但是,只有在實際迭代它們時才會水合 (hydrate) 個別的 Eloquent 模型。因此,在遍歷游標時,記憶體中一次只保留一個 Eloquent 模型。

由於 cursor 方法一次只在記憶體中保留一個 Eloquent 模型,因此它無法預先載入關聯。如果您需要預先載入關聯,請考慮改用lazy 方法

在內部,cursor 方法使用 PHP 產生器 來實作此功能。

1use App\Models\Flight;
2 
3foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
4 // ...
5}

cursor 返回一個 Illuminate\Support\LazyCollection 實例。惰性集合 允許您使用許多在典型 Laravel 集合上可用的集合方法,同時一次僅將一個模型載入記憶體中。

1use App\Models\User;
2 
3$users = User::cursor()->filter(function (User $user) {
4 return $user->id > 500;
5});
6 
7foreach ($users as $user) {
8 echo $user->id;
9}

儘管 cursor 方法比常規查詢使用更少的記憶體(因為一次只保留一個 Eloquent 模型在記憶體中),但它最終仍然會耗盡記憶體。這是由於 PHP 的 PDO 驅動程式在內部緩存了所有原始查詢結果在其緩衝區中。如果您正在處理非常大量的 Eloquent 記錄,請考慮改用lazy 方法

進階子查詢

子查詢選擇

Eloquent 也提供進階的子查詢支援,允許您在單一查詢中從相關表格中提取資訊。例如,假設我們有一個航班 destinations 表格和一個前往目的地的 flights 表格。flights 表格包含一個 arrived_at 欄位,指示航班何時抵達目的地。

使用查詢建構器的 selectaddSelect 方法提供的子查詢功能,我們可以選擇所有的 destinations 和最近抵達該目的地的航班名稱,只需單一查詢。

1use App\Models\Destination;
2use App\Models\Flight;
3 
4return Destination::addSelect(['last_flight' => Flight::select('name')
5 ->whereColumn('destination_id', 'destinations.id')
6 ->orderByDesc('arrived_at')
7 ->limit(1)
8])->get();

子查詢排序

此外,查詢建構器的 orderBy 函數支援子查詢。繼續使用我們的航班範例,我們可以利用此功能根據最後一班航班抵達目的地的時間來排序所有目的地。同樣,這可以在執行單一資料庫查詢時完成。

1return Destination::orderByDesc(
2 Flight::select('arrived_at')
3 ->whereColumn('destination_id', 'destinations.id')
4 ->orderByDesc('arrived_at')
5 ->limit(1)
6)->get();

檢索單一模型 / 聚合

除了檢索符合給定查詢的所有記錄外,您還可以透過 findfirstfirstWhere 方法檢索單一記錄。這些方法不是返回模型集合,而是返回單一模型實例。

1use App\Models\Flight;
2 
3// Retrieve a model by its primary key...
4$flight = Flight::find(1);
5 
6// Retrieve the first model matching the query constraints...
7$flight = Flight::where('active', 1)->first();
8 
9// Alternative to retrieving the first model matching the query constraints...
10$flight = Flight::firstWhere('active', 1);

有時,如果沒有找到任何結果,您可能希望執行其他操作。findOrfirstOr 方法將返回單一模型實例,或者,如果沒有找到任何結果,則執行給定的閉包。閉包返回的值將被視為該方法的結果。

1$flight = Flight::findOr(1, function () {
2 // ...
3});
4 
5$flight = Flight::where('legs', '>', 3)->firstOr(function () {
6 // ...
7});

找不到例外

有時,如果找不到模型,您可能希望拋出例外。這在路由或控制器中特別有用。findOrFailfirstOrFail 方法將檢索查詢的第一個結果;但是,如果找不到任何結果,將拋出 Illuminate\Database\Eloquent\ModelNotFoundException

1$flight = Flight::findOrFail(1);
2 
3$flight = Flight::where('legs', '>', 3)->firstOrFail();

如果 ModelNotFoundException 沒有被捕獲,則會自動將 404 HTTP 回應傳送回客戶端。

1use App\Models\Flight;
2 
3Route::get('/api/flights/{id}', function (string $id) {
4 return Flight::findOrFail($id);
5});

檢索或建立模型

firstOrCreate 方法將嘗試使用給定的欄位/值對來定位資料庫記錄。如果在資料庫中找不到該模型,則將插入一筆記錄,其屬性是將第一個陣列參數與可選的第二個陣列參數合併後的結果。

firstOrNew 方法與 firstOrCreate 類似,將嘗試在資料庫中定位與給定屬性匹配的記錄。但是,如果找不到模型,將返回一個新的模型實例。請注意,firstOrNew 返回的模型尚未持久化到資料庫中。您需要手動調用 save 方法來持久化它。

1use App\Models\Flight;
2 
3// Retrieve flight by name or create it if it doesn't exist...
4$flight = Flight::firstOrCreate([
5 'name' => 'London to Paris'
6]);
7 
8// Retrieve flight by name or create it with the name, delayed, and arrival_time attributes...
9$flight = Flight::firstOrCreate(
10 ['name' => 'London to Paris'],
11 ['delayed' => 1, 'arrival_time' => '11:30']
12);
13 
14// Retrieve flight by name or instantiate a new Flight instance...
15$flight = Flight::firstOrNew([
16 'name' => 'London to Paris'
17]);
18 
19// Retrieve flight by name or instantiate with the name, delayed, and arrival_time attributes...
20$flight = Flight::firstOrNew(
21 ['name' => 'Tokyo to Sydney'],
22 ['delayed' => 1, 'arrival_time' => '11:30']
23);

檢索聚合

當與 Eloquent 模型互動時,您也可以使用 Laravel 查詢建構器 提供的 countsummax 和其他聚合方法。正如您可能預期的那樣,這些方法返回的是純量值,而不是 Eloquent 模型實例。

1$count = Flight::where('active', 1)->count();
2 
3$max = Flight::where('active', 1)->max('price');

插入和更新模型

插入

當然,當使用 Eloquent 時,我們不僅需要從資料庫中檢索模型。我們還需要插入新的記錄。值得慶幸的是,Eloquent 使這變得簡單。要將新記錄插入資料庫,您應該實例化一個新的模型實例並設定模型上的屬性。然後,調用模型實例上的 save 方法。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Http\Controllers\Controller;
6use App\Models\Flight;
7use Illuminate\Http\RedirectResponse;
8use Illuminate\Http\Request;
9 
10class FlightController extends Controller
11{
12 /**
13 * Store a new flight in the database.
14 */
15 public function store(Request $request): RedirectResponse
16 {
17 // Validate the request...
18 
19 $flight = new Flight;
20 
21 $flight->name = $request->name;
22 
23 $flight->save();
24 
25 return redirect('/flights');
26 }
27}

在此範例中,我們將來自傳入 HTTP 請求的 name 欄位賦值給 App\Models\Flight 模型實例的 name 屬性。當我們調用 save 方法時,將在資料庫中插入一筆記錄。當調用 save 方法時,模型的 created_atupdated_at 時間戳記將自動設定,因此無需手動設定它們。

或者,您可以使用 create 方法使用單個 PHP 語句「儲存」一個新模型。插入的模型實例將由 create 方法返回給您。

1use App\Models\Flight;
2 
3$flight = Flight::create([
4 'name' => 'London to Paris',
5]);

但是,在使用 create 方法之前,您需要在您的模型類別中指定 fillableguarded 屬性。之所以需要這些屬性,是因為所有 Eloquent 模型預設都受到批量賦值漏洞的保護。要了解有關批量賦值的更多資訊,請查閱批量賦值文件

更新

save 方法也可用於更新資料庫中已存在的模型。要更新模型,您應該檢索它並設定您希望更新的任何屬性。然後,您應該調用模型的 save 方法。同樣,updated_at 時間戳記將自動更新,因此無需手動設定其值。

1use App\Models\Flight;
2 
3$flight = Flight::find(1);
4 
5$flight->name = 'Paris to London';
6 
7$flight->save();

有時,您可能需要更新現有模型,或者在沒有匹配模型的情況下建立新模型。與 firstOrCreate 方法類似,updateOrCreate 方法會持久化模型,因此無需手動調用 save 方法。

在下面的範例中,如果存在出發地為 Oakland 且目的地為 San Diego 的航班,則其 pricediscounted 欄位將被更新。如果不存在這樣的航班,則將建立一個新的航班,其屬性是將第一個參數陣列與第二個參數陣列合併後的結果。

1$flight = Flight::updateOrCreate(
2 ['departure' => 'Oakland', 'destination' => 'San Diego'],
3 ['price' => 99, 'discounted' => 1]
4);

批量更新

也可以針對符合給定查詢的模型執行更新。在此範例中,所有 activedestinationSan Diego 的航班都將被標記為延遲。

1Flight::where('active', 1)
2 ->where('destination', 'San Diego')
3 ->update(['delayed' => 1]);

update 方法預期一個欄位和值對的陣列,表示應該更新的欄位。update 方法返回受影響的行數。

當透過 Eloquent 發出批量更新時,不會為更新後的模型觸發 savingsavedupdatingupdated 模型事件。這是因為在發出批量更新時,模型實際上從未被檢索過。

檢查屬性變更

Eloquent 提供了 isDirtyisCleanwasChanged 方法,用於檢查模型的內部狀態,並確定其屬性自模型最初檢索以來發生了哪些變更。

isDirty 方法確定模型的任何屬性自模型檢索以來是否已更改。您可以將特定的屬性名稱或屬性陣列傳遞給 isDirty 方法,以確定是否有任何屬性是「dirty」。isClean 方法將確定屬性自模型檢索以來是否保持不變。此方法也接受可選的屬性參數。

1use App\Models\User;
2 
3$user = User::create([
4 'first_name' => 'Taylor',
5 'last_name' => 'Otwell',
6 'title' => 'Developer',
7]);
8 
9$user->title = 'Painter';
10 
11$user->isDirty(); // true
12$user->isDirty('title'); // true
13$user->isDirty('first_name'); // false
14$user->isDirty(['first_name', 'title']); // true
15 
16$user->isClean(); // false
17$user->isClean('title'); // false
18$user->isClean('first_name'); // true
19$user->isClean(['first_name', 'title']); // false
20 
21$user->save();
22 
23$user->isDirty(); // false
24$user->isClean(); // true

wasChanged 方法確定在目前請求週期內最後一次儲存模型時是否更改了任何屬性。如果需要,您可以傳遞屬性名稱以查看特定屬性是否已更改。

1$user = User::create([
2 'first_name' => 'Taylor',
3 'last_name' => 'Otwell',
4 'title' => 'Developer',
5]);
6 
7$user->title = 'Painter';
8 
9$user->save();
10 
11$user->wasChanged(); // true
12$user->wasChanged('title'); // true
13$user->wasChanged(['title', 'slug']); // true
14$user->wasChanged('first_name'); // false
15$user->wasChanged(['first_name', 'title']); // true

getOriginal 方法返回一個陣列,其中包含模型的原始屬性,無論模型自檢索以來發生了哪些變更。如果需要,您可以傳遞特定的屬性名稱以取得特定屬性的原始值。

1$user = User::find(1);
2 
3$user->name; // John
4$user->email; // [email protected]
5 
6$user->name = "Jack";
7$user->name; // Jack
8 
9$user->getOriginal('name'); // John
10$user->getOriginal(); // Array of original attributes...

大量賦值

您可以使用 create 方法使用單個 PHP 語句「儲存」一個新模型。插入的模型實例將由該方法返回給您。

1use App\Models\Flight;
2 
3$flight = Flight::create([
4 'name' => 'London to Paris',
5]);

但是,在使用 create 方法之前,您需要在您的模型類別中指定 fillableguarded 屬性。之所以需要這些屬性,是因為所有 Eloquent 模型預設都受到批量賦值漏洞的保護。

當使用者傳遞一個意外的 HTTP 請求欄位,並且該欄位更改了您不期望的資料庫中的欄位時,就會發生批量賦值漏洞。例如,惡意使用者可能會透過 HTTP 請求發送 is_admin 參數,然後將其傳遞給模型的 create 方法,從而允許使用者將自己提升為管理員。

因此,首先,您應該定義您想要進行批量賦值的模型屬性。您可以使用模型上的 $fillable 屬性來執行此操作。例如,讓我們將我們的 Flight 模型的 name 屬性設為可批量賦值。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class Flight extends Model
8{
9 /**
10 * The attributes that are mass assignable.
11 *
12 * @var array<int, string>
13 */
14 protected $fillable = ['name'];
15}

一旦您指定了哪些屬性是可批量賦值的,您就可以使用 create 方法在資料庫中插入新記錄。create 方法返回新建立的模型實例。

1$flight = Flight::create(['name' => 'London to Paris']);

如果您已經有一個模型實例,您可以使用 fill 方法使用屬性陣列填充它。

1$flight->fill(['name' => 'Amsterdam to Frankfurt']);

批量賦值和 JSON 欄位

當賦值 JSON 欄位時,必須在模型的 $fillable 陣列中指定每個欄位的可批量賦值鍵。為了安全起見,當使用 guarded 屬性時,Laravel 不支援更新巢狀 JSON 屬性。

1/**
2 * The attributes that are mass assignable.
3 *
4 * @var array<int, string>
5 */
6protected $fillable = [
7 'options->enabled',
8];

允許批量賦值

如果您想要將所有屬性都設為可批量賦值,您可以將模型的 $guarded 屬性定義為空陣列。如果您選擇解除保護您的模型,您應該特別注意始終手動製作傳遞給 Eloquent 的 fillcreateupdate 方法的陣列。

1/**
2 * The attributes that aren't mass assignable.
3 *
4 * @var array<string>|bool
5 */
6protected $guarded = [];

批量賦值例外

預設情況下,在執行批量賦值操作時,$fillable 陣列中未包含的屬性將被靜默丟棄。在生產環境中,這是預期的行為;但是,在本地開發期間,這可能會導致混淆,不明白為什麼模型變更沒有生效。

如果您希望,您可以指示 Laravel 在嘗試填充不可填充的屬性時拋出例外,方法是調用 preventSilentlyDiscardingAttributes 方法。通常,此方法應在應用程式的 AppServiceProvider 類別的 boot 方法中調用。

1use Illuminate\Database\Eloquent\Model;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
9}

Upserts(更新插入)

Eloquent 的 upsert 方法可用於在單一原子操作中更新或建立記錄。該方法的第一個參數包含要插入或更新的值,而第二個參數列出唯一識別關聯表格中記錄的欄位。該方法的第三個也是最後一個參數是一個陣列,其中包含如果資料庫中已存在匹配記錄時應更新的欄位。如果模型上啟用了時間戳記,則 upsert 方法將自動設定 created_atupdated_at 時間戳記。

1Flight::upsert([
2 ['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
3 ['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
4], uniqueBy: ['departure', 'destination'], update: ['price']);

除了 SQL Server 之外的所有資料庫都要求 upsert 方法的第二個參數中的欄位具有「primary」或「unique」索引。此外,MariaDB 和 MySQL 資料庫驅動程式會忽略 upsert 方法的第二個參數,並且始終使用表格的「primary」和「unique」索引來檢測現有記錄。

刪除模型

要刪除模型,您可以調用模型實例上的 delete 方法。

1use App\Models\Flight;
2 
3$flight = Flight::find(1);
4 
5$flight->delete();

通過主鍵刪除現有模型

在上面的範例中,我們在呼叫 delete 方法之前,先從資料庫中取出模型。但是,如果您知道模型的主鍵,您可以不用明確地取出模型,直接呼叫 destroy 方法來刪除模型。除了接受單一主鍵之外,destroy 方法也接受多個主鍵、主鍵陣列或 集合 的主鍵。

1Flight::destroy(1);
2 
3Flight::destroy(1, 2, 3);
4 
5Flight::destroy([1, 2, 3]);
6 
7Flight::destroy(collect([1, 2, 3]));

如果您正在使用 軟刪除模型,您可以使用 forceDestroy 方法永久刪除模型。

1Flight::forceDestroy(1);

destroy 方法會個別載入每個模型,並呼叫 delete 方法,以便為每個模型正確地發送 deletingdeleted 事件。

使用查詢刪除模型

當然,您可以建立 Eloquent 查詢來刪除所有符合您查詢條件的模型。在這個範例中,我們將刪除所有標記為非活動狀態的航班。與大量更新一樣,大量刪除不會為被刪除的模型發送模型事件。

1$deleted = Flight::where('active', 0)->delete();

若要刪除資料表中的所有模型,您應該執行不新增任何條件的查詢。

1$deleted = Flight::query()->delete();

當透過 Eloquent 執行大量刪除語句時,deletingdeleted 模型事件將不會為已刪除的模型發送。這是因為在執行刪除語句時,模型實際上從未被取出。

軟刪除

除了實際從資料庫中移除記錄外,Eloquent 也可以「軟刪除」模型。當模型被軟刪除時,它們實際上並未從資料庫中移除。相反地,模型上會設定一個 deleted_at 屬性,指示模型被「刪除」的日期和時間。若要為模型啟用軟刪除,請將 Illuminate\Database\Eloquent\SoftDeletes trait 新增到模型中。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6use Illuminate\Database\Eloquent\SoftDeletes;
7 
8class Flight extends Model
9{
10 use SoftDeletes;
11}

SoftDeletes trait 會自動將 deleted_at 屬性轉換為 DateTime / Carbon 實例。

您也應該將 deleted_at 欄位新增到您的資料庫表格。Laravel schema builder 包含一個輔助方法來建立此欄位。

1use Illuminate\Database\Schema\Blueprint;
2use Illuminate\Support\Facades\Schema;
3 
4Schema::table('flights', function (Blueprint $table) {
5 $table->softDeletes();
6});
7 
8Schema::table('flights', function (Blueprint $table) {
9 $table->dropSoftDeletes();
10});

現在,當您在模型上呼叫 delete 方法時,deleted_at 欄位將會設定為目前的日期和時間。但是,模型的資料庫記錄將會保留在表格中。當查詢使用軟刪除的模型時,軟刪除的模型將會自動從所有查詢結果中排除。

若要判斷給定的模型實例是否已被軟刪除,您可以使用 trashed 方法。

1if ($flight->trashed()) {
2 // ...
3}

還原軟刪除的模型

有時您可能希望「取消刪除」一個軟刪除的模型。若要還原軟刪除的模型,您可以在模型實例上呼叫 restore 方法。restore 方法會將模型的 deleted_at 欄位設定為 null

1$flight->restore();

您也可以在查詢中使用 restore 方法來還原多個模型。同樣地,與其他「大量」操作一樣,這將不會為已還原的模型發送任何模型事件。

1Flight::withTrashed()
2 ->where('airline_id', 1)
3 ->restore();

restore 方法也可以在建立 關聯 查詢時使用。

1$flight->history()->restore();

永久刪除模型

有時您可能需要真正從資料庫中移除模型。您可以使用 forceDelete 方法從資料庫表格中永久移除軟刪除的模型。

1$flight->forceDelete();

您也可以在建立 Eloquent 關聯查詢時使用 forceDelete 方法。

1$flight->history()->forceDelete();

查詢軟刪除的模型

包含軟刪除的模型

如上所述,軟刪除的模型將會自動從查詢結果中排除。但是,您可以透過在查詢中呼叫 withTrashed 方法,強制將軟刪除的模型包含在查詢結果中。

1use App\Models\Flight;
2 
3$flights = Flight::withTrashed()
4 ->where('account_id', 1)
5 ->get();

withTrashed 方法也可以在建立 關聯 查詢時呼叫。

1$flight->history()->withTrashed()->get();

僅檢索軟刪除的模型

onlyTrashed 方法將會檢索僅限軟刪除的模型。

1$flights = Flight::onlyTrashed()
2 ->where('airline_id', 1)
3 ->get();

修剪模型

有時您可能想要定期刪除不再需要的模型。為了實現這一點,您可以將 Illuminate\Database\Eloquent\PrunableIlluminate\Database\Eloquent\MassPrunable trait 新增到您想要定期修剪的模型中。將其中一個 trait 新增到模型後,實作一個 prunable 方法,該方法會回傳一個 Eloquent 查詢建構器,以解析不再需要的模型。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Prunable;
8 
9class Flight extends Model
10{
11 use Prunable;
12 
13 /**
14 * Get the prunable model query.
15 */
16 public function prunable(): Builder
17 {
18 return static::where('created_at', '<=', now()->subMonth());
19 }
20}

在將模型標記為 Prunable 時,您也可以在模型上定義一個 pruning 方法。此方法將在模型被刪除之前呼叫。此方法可用於刪除與模型相關聯的任何其他資源,例如儲存的檔案,然後再從資料庫中永久移除模型。

1/**
2 * Prepare the model for pruning.
3 */
4protected function pruning(): void
5{
6 // ...
7}

設定您的可修剪模型後,您應該在應用程式的 routes/console.php 檔案中排程 model:prune Artisan 命令。您可以自由選擇應該執行此命令的適當間隔。

1use Illuminate\Support\Facades\Schedule;
2 
3Schedule::command('model:prune')->daily();

在幕後,model:prune 命令將會自動偵測您應用程式 app/Models 目錄中的「Prunable」模型。如果您的模型位於不同的位置,您可以使用 --model 選項來指定模型類別名稱。

1Schedule::command('model:prune', [
2 '--model' => [Address::class, Flight::class],
3])->daily();

如果您希望在修剪所有其他偵測到的模型時排除某些模型,您可以使用 --except 選項。

1Schedule::command('model:prune', [
2 '--except' => [Address::class, Flight::class],
3])->daily();

您可以透過執行帶有 --pretend 選項的 model:prune 命令來測試您的 prunable 查詢。在模擬執行時,model:prune 命令只會報告如果實際執行該命令將會修剪多少筆記錄。

1php artisan model:prune --pretend

如果軟刪除的模型符合可修剪查詢,則會被永久刪除 (forceDelete)。

大量修剪

當模型標記為 Illuminate\Database\Eloquent\MassPrunable trait 時,模型會使用大量刪除查詢從資料庫中刪除。因此,pruning 方法將不會被調用,deletingdeleted 模型事件也不會被發送。這是因為模型在刪除之前實際上從未被檢索,因此使修剪過程更加有效率。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\MassPrunable;
8 
9class Flight extends Model
10{
11 use MassPrunable;
12 
13 /**
14 * Get the prunable model query.
15 */
16 public function prunable(): Builder
17 {
18 return static::where('created_at', '<=', now()->subMonth());
19 }
20}

複製模型

您可以使用 replicate 方法建立現有模型實例的未儲存副本。當您有許多屬性相同的模型實例時,此方法特別有用。

1use App\Models\Address;
2 
3$shipping = Address::create([
4 'type' => 'shipping',
5 'line_1' => '123 Example Street',
6 'city' => 'Victorville',
7 'state' => 'CA',
8 'postcode' => '90001',
9]);
10 
11$billing = $shipping->replicate()->fill([
12 'type' => 'billing'
13]);
14 
15$billing->save();

若要從複製到新模型的屬性中排除一個或多個屬性,您可以將陣列傳遞給 replicate 方法。

1$flight = Flight::create([
2 'destination' => 'LAX',
3 'origin' => 'LHR',
4 'last_flown' => '2020-03-04 11:00:00',
5 'last_pilot_id' => 747,
6]);
7 
8$flight = $flight->replicate([
9 'last_flown',
10 'last_pilot_id'
11]);

查詢範圍

全域範圍

全域作用域允許您為給定模型的所有查詢新增約束。Laravel 自己的 軟刪除 功能使用全域作用域,僅從資料庫中檢索「未刪除」的模型。編寫您自己的全域作用域可以提供一種方便、簡單的方式,確保給定模型的每個查詢都收到某些約束。

產生作用域

若要產生新的全域作用域,您可以調用 make:scope Artisan 命令,這會將產生的作用域放置在您應用程式的 app/Models/Scopes 目錄中。

1php artisan make:scope AncientScope

編寫全域作用域

編寫全域作用域很簡單。首先,使用 make:scope 命令產生一個類別,該類別實作 Illuminate\Database\Eloquent\Scope 介面。Scope 介面要求您實作一個方法:applyapply 方法可以根據需要將 where 約束或其他類型的子句新增到查詢中。

1<?php
2 
3namespace App\Models\Scopes;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7use Illuminate\Database\Eloquent\Scope;
8 
9class AncientScope implements Scope
10{
11 /**
12 * Apply the scope to a given Eloquent query builder.
13 */
14 public function apply(Builder $builder, Model $model): void
15 {
16 $builder->where('created_at', '<', now()->subYears(2000));
17 }
18}

如果您的全域作用域正在將欄位新增到查詢的 select 子句中,您應該使用 addSelect 方法而不是 select。這將防止意外地替換查詢現有的 select 子句。

套用全域作用域

若要將全域作用域指派給模型,您只需將 ScopedBy 屬性放置在模型上即可。

1<?php
2 
3namespace App\Models;
4 
5use App\Models\Scopes\AncientScope;
6use Illuminate\Database\Eloquent\Attributes\ScopedBy;
7 
8#[ScopedBy([AncientScope::class])]
9class User extends Model
10{
11 //
12}

或者,您可以透過覆寫模型的 booted 方法並調用模型的 addGlobalScope 方法來手動註冊全域作用域。addGlobalScope 方法接受您的作用域實例作為其唯一參數。

1<?php
2 
3namespace App\Models;
4 
5use App\Models\Scopes\AncientScope;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The "booted" method of the model.
12 */
13 protected static function booted(): void
14 {
15 static::addGlobalScope(new AncientScope);
16 }
17}

在將上述範例中的作用域新增到 App\Models\User 模型後,呼叫 User::all() 方法將會執行以下 SQL 查詢。

1select * from `users` where `created_at` < 0021-02-18 00:00:00

匿名全域作用域

Eloquent 也允許您使用閉包定義全域作用域,這對於不需要單獨類別的簡單作用域特別有用。當使用閉包定義全域作用域時,您應該提供您自己選擇的作用域名稱作為 addGlobalScope 方法的第一個參數。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * The "booted" method of the model.
12 */
13 protected static function booted(): void
14 {
15 static::addGlobalScope('ancient', function (Builder $builder) {
16 $builder->where('created_at', '<', now()->subYears(2000));
17 });
18 }
19}

移除全域作用域

如果您想要移除給定查詢的全域作用域,您可以使用 withoutGlobalScope 方法。此方法接受全域作用域的類別名稱作為其唯一參數。

1User::withoutGlobalScope(AncientScope::class)->get();

或者,如果您使用閉包定義全域作用域,您應該傳遞您指派給全域作用域的字串名稱。

1User::withoutGlobalScope('ancient')->get();

如果您想要移除數個甚至所有查詢的全域作用域,您可以使用 withoutGlobalScopes 方法。

1// Remove all of the global scopes...
2User::withoutGlobalScopes()->get();
3 
4// Remove some of the global scopes...
5User::withoutGlobalScopes([
6 FirstScope::class, SecondScope::class
7])->get();

本地範圍

局部作用域允許您定義常用的查詢約束集合,您可以在整個應用程式中輕鬆重複使用。例如,您可能需要頻繁地檢索所有被認為「熱門」的使用者。若要定義作用域,請在 Eloquent 模型方法前加上 scope 前綴。

作用域應始終回傳相同的查詢建構器實例或 void

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * Scope a query to only include popular users.
12 */
13 public function scopePopular(Builder $query): void
14 {
15 $query->where('votes', '>', 100);
16 }
17 
18 /**
19 * Scope a query to only include active users.
20 */
21 public function scopeActive(Builder $query): void
22 {
23 $query->where('active', 1);
24 }
25}

使用局部作用域

一旦定義了作用域,您就可以在查詢模型時呼叫作用域方法。但是,在呼叫方法時,您不應包含 scope 前綴。您甚至可以鏈式呼叫各種作用域。

1use App\Models\User;
2 
3$users = User::popular()->active()->orderBy('created_at')->get();

透過 or 查詢運算子組合多個 Eloquent 模型作用域可能需要使用閉包來實現正確的 邏輯分組

1$users = User::popular()->orWhere(function (Builder $query) {
2 $query->active();
3})->get();

但是,由於這可能會很麻煩,Laravel 提供了一個「更高階」的 orWhere 方法,讓您可以流暢地將作用域鏈接在一起,而無需使用閉包。

1$users = User::popular()->orWhere->active()->get();

動態作用域

有時您可能希望定義一個接受參數的作用域。若要開始使用,只需將您的額外參數新增到您的作用域方法的簽名中。作用域參數應在 $query 參數之後定義。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class User extends Model
9{
10 /**
11 * Scope a query to only include users of a given type.
12 */
13 public function scopeOfType(Builder $query, string $type): void
14 {
15 $query->where('type', $type);
16 }
17}

一旦將預期的參數新增到您的作用域方法的簽名中,您就可以在呼叫作用域時傳遞參數。

1$users = User::ofType('admin')->get();

待定屬性

如果您想使用作用域來建立與用於約束作用域的屬性相同的模型,您可以在建立作用域查詢時使用 withAttributes 方法。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Builder;
6use Illuminate\Database\Eloquent\Model;
7 
8class Post extends Model
9{
10 /**
11 * Scope the query to only include drafts.
12 */
13 public function scopeDraft(Builder $query): void
14 {
15 $query->withAttributes([
16 'hidden' => true,
17 ]);
18 }
19}

withAttributes 方法會使用給定的屬性將 where 子句約束新增到查詢中,並且還會將給定的屬性新增到透過作用域建立的任何模型中。

1$draft = Post::draft()->create(['title' => 'In Progress']);
2 
3$draft->hidden; // true

比較模型

有時您可能需要判斷兩個模型是否「相同」。isisNot 方法可用於快速驗證兩個模型是否具有相同的主鍵、表格和資料庫連線。

1if ($post->is($anotherPost)) {
2 // ...
3}
4 
5if ($post->isNot($anotherPost)) {
6 // ...
7}

當使用 belongsTohasOnemorphTomorphOne 關聯 時,isisNot 方法也可用。當您想要比較相關模型而無需發出查詢來檢索該模型時,此方法特別有用。

1if ($post->author()->is($user)) {
2 // ...
3}

事件

想要將您的 Eloquent 事件直接廣播到您的客戶端應用程式嗎?請查看 Laravel 的 模型事件廣播

Eloquent 模型發送多個事件,讓您可以掛鉤到模型生命週期中的以下時刻:retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedtrashedforceDeletingforceDeletedrestoringrestoredreplicating

當從資料庫中檢索現有模型時,將會發送 retrieved 事件。當第一次儲存新模型時,將會發送 creatingcreated 事件。當現有模型被修改並呼叫 save 方法時,將會發送 updating / updated 事件。當模型被建立或更新時(即使模型的屬性沒有被更改),將會發送 saving / saved 事件。以 -ing 結尾的事件會在模型的任何變更被持久化之前發送,而以 -ed 結尾的事件會在模型的變更被持久化之後發送。

若要開始監聽模型事件,請在您的 Eloquent 模型上定義 $dispatchesEvents 屬性。此屬性將 Eloquent 模型生命週期的各個點對應到您自己的 事件類別。每個模型事件類別都應該期望透過其建構子接收受影響模型的實例。

1<?php
2 
3namespace App\Models;
4 
5use App\Events\UserDeleted;
6use App\Events\UserSaved;
7use Illuminate\Foundation\Auth\User as Authenticatable;
8use Illuminate\Notifications\Notifiable;
9 
10class User extends Authenticatable
11{
12 use Notifiable;
13 
14 /**
15 * The event map for the model.
16 *
17 * @var array<string, string>
18 */
19 protected $dispatchesEvents = [
20 'saved' => UserSaved::class,
21 'deleted' => UserDeleted::class,
22 ];
23}

在定義和對應您的 Eloquent 事件後,您可以使用 事件監聽器 來處理事件。

當透過 Eloquent 發出大量更新或刪除查詢時,savedupdateddeletingdeleted 模型事件將不會為受影響的模型發送。這是因為在執行大量更新或刪除時,模型實際上從未被檢索。

使用閉包

您可以註冊在發送各種模型事件時執行的閉包,而不是使用自訂事件類別。通常,您應該在模型的 booted 方法中註冊這些閉包。

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Database\Eloquent\Model;
6 
7class User extends Model
8{
9 /**
10 * The "booted" method of the model.
11 */
12 protected static function booted(): void
13 {
14 static::created(function (User $user) {
15 // ...
16 });
17 }
18}

如果需要,您可以在註冊模型事件時使用 可佇列的匿名事件監聽器。這將指示 Laravel 使用您應用程式的 佇列 在背景中執行模型事件監聽器。

1use function Illuminate\Events\queueable;
2 
3static::created(queueable(function (User $user) {
4 // ...
5}));

Observers(觀察器)

定義觀察器

如果您正在監聽給定模型上的許多事件,您可以使用觀察器將您的所有監聽器分組到單一類別中。觀察器類別具有反映您希望監聽的 Eloquent 事件的方法名稱。這些方法中的每一個都接收受影響的模型作為其唯一參數。make:observer Artisan 命令是建立新觀察器類別的最簡單方法。

1php artisan make:observer UserObserver --model=User

此命令會將新的觀察器放置在您的 app/Observers 目錄中。如果此目錄不存在,Artisan 將為您建立它。您的新觀察器將如下所示。

1<?php
2 
3namespace App\Observers;
4 
5use App\Models\User;
6 
7class UserObserver
8{
9 /**
10 * Handle the User "created" event.
11 */
12 public function created(User $user): void
13 {
14 // ...
15 }
16 
17 /**
18 * Handle the User "updated" event.
19 */
20 public function updated(User $user): void
21 {
22 // ...
23 }
24 
25 /**
26 * Handle the User "deleted" event.
27 */
28 public function deleted(User $user): void
29 {
30 // ...
31 }
32 
33 /**
34 * Handle the User "restored" event.
35 */
36 public function restored(User $user): void
37 {
38 // ...
39 }
40 
41 /**
42 * Handle the User "forceDeleted" event.
43 */
44 public function forceDeleted(User $user): void
45 {
46 // ...
47 }
48}

若要註冊觀察器,您可以將 ObservedBy 屬性放置在相應的模型上。

1use App\Observers\UserObserver;
2use Illuminate\Database\Eloquent\Attributes\ObservedBy;
3 
4#[ObservedBy([UserObserver::class])]
5class User extends Authenticatable
6{
7 //
8}

或者,您可以透過調用您希望觀察的模型上的 observe 方法來手動註冊觀察器。您可以在應用程式的 AppServiceProvider 類別的 boot 方法中註冊觀察器。

1use App\Models\User;
2use App\Observers\UserObserver;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 User::observe(UserObserver::class);
10}

觀察器可以監聽其他事件,例如 savingretrieved。這些事件在 事件 文件中描述。

觀察器和資料庫交易

當模型在資料庫交易中建立時,您可能希望指示觀察器僅在資料庫交易提交後才執行其事件處理常式。您可以透過在觀察器上實作 ShouldHandleEventsAfterCommit 介面來完成此操作。如果資料庫交易未進行中,事件處理常式將立即執行。

1<?php
2 
3namespace App\Observers;
4 
5use App\Models\User;
6use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
7 
8class UserObserver implements ShouldHandleEventsAfterCommit
9{
10 /**
11 * Handle the User "created" event.
12 */
13 public function created(User $user): void
14 {
15 // ...
16 }
17}

靜音事件

您偶爾可能需要暫時「靜音」模型發送的所有事件。您可以使用 withoutEvents 方法來實現此目的。withoutEvents 方法接受閉包作為其唯一參數。在此閉包中執行的任何程式碼都不會發送模型事件,並且閉包回傳的任何值都將由 withoutEvents 方法回傳。

1use App\Models\User;
2 
3$user = User::withoutEvents(function () {
4 User::findOrFail(1)->delete();
5 
6 return User::find(2);
7});

儲存單一模型而不發送事件

有時候您可能希望「儲存」給定的模型,而不觸發任何事件。您可以使用 saveQuietly 方法來完成此操作

1$user = User::findOrFail(1);
2 
3$user->name = 'Victoria Faith';
4 
5$user->saveQuietly();

您也可以「更新」、「刪除」、「軟刪除」、「還原」和「複製」給定的模型,而不觸發任何事件

1$user->deleteQuietly();
2$user->forceDeleteQuietly();
3$user->restoreQuietly();

Laravel 是最具生產力的方式來
建構、部署和監控軟體。